pomera-ai-commander 1.2.7 → 1.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/pomera-create-shortcut.js +51 -0
- package/bin/pomera.js +68 -0
- package/core/database_schema.py +24 -1
- package/core/database_schema_manager.py +4 -2
- package/core/database_settings_manager.py +25 -2
- package/core/dialog_manager.py +4 -4
- package/core/efficient_line_numbers.py +5 -4
- package/core/load_presets_dialog.py +460 -0
- package/core/mcp/tool_registry.py +327 -0
- package/core/settings_defaults_registry.py +159 -15
- package/core/tool_search_widget.py +85 -5
- package/create_shortcut.py +12 -4
- package/mcp.json +1 -1
- package/package.json +4 -2
- package/pomera.py +760 -25
- package/tools/base64_tools.py +4 -4
- package/tools/case_tool.py +4 -4
- package/tools/curl_settings.py +12 -1
- package/tools/curl_tool.py +176 -11
- package/tools/notes_widget.py +8 -1
- package/tools/tool_loader.py +20 -9
- package/tools/url_content_reader.py +402 -0
- package/tools/web_search.py +522 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pomera AI Commander - Desktop Shortcut Creator
|
|
5
|
+
*
|
|
6
|
+
* This script creates a desktop shortcut for the Pomera GUI.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { spawn } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
// Get the path to create_shortcut.py
|
|
13
|
+
const shortcutScript = path.join(__dirname, '..', 'create_shortcut.py');
|
|
14
|
+
|
|
15
|
+
// Find Python executable
|
|
16
|
+
function findPython() {
|
|
17
|
+
const { execSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
execSync('python3 --version', { stdio: 'ignore' });
|
|
21
|
+
return 'python3';
|
|
22
|
+
} catch (e) {
|
|
23
|
+
try {
|
|
24
|
+
execSync('python --version', { stdio: 'ignore' });
|
|
25
|
+
return 'python';
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error('Error: Python is not installed or not in PATH');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const pythonCmd = findPython();
|
|
34
|
+
|
|
35
|
+
// Get command line arguments
|
|
36
|
+
const args = process.argv.slice(2);
|
|
37
|
+
|
|
38
|
+
// Spawn the Python script
|
|
39
|
+
const proc = spawn(pythonCmd, [shortcutScript, ...args], {
|
|
40
|
+
stdio: 'inherit',
|
|
41
|
+
cwd: path.join(__dirname, '..')
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
proc.on('close', (code) => {
|
|
45
|
+
process.exit(code || 0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
proc.on('error', (err) => {
|
|
49
|
+
console.error('Failed to create shortcut:', err.message);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
package/bin/pomera.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pomera AI Commander - GUI launcher
|
|
5
|
+
*
|
|
6
|
+
* This script launches the Pomera GUI application.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { spawn } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
// Get the path to pomera.py
|
|
13
|
+
const pomeraPath = path.join(__dirname, '..', 'pomera.py');
|
|
14
|
+
|
|
15
|
+
// Find Python executable
|
|
16
|
+
function findPython() {
|
|
17
|
+
const { execSync } = require('child_process');
|
|
18
|
+
|
|
19
|
+
// Try pythonw first (Windows - no console)
|
|
20
|
+
if (process.platform === 'win32') {
|
|
21
|
+
try {
|
|
22
|
+
execSync('pythonw --version', { stdio: 'ignore' });
|
|
23
|
+
return 'pythonw';
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Fall through
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Try python3 (Linux/macOS)
|
|
30
|
+
try {
|
|
31
|
+
execSync('python3 --version', { stdio: 'ignore' });
|
|
32
|
+
return 'python3';
|
|
33
|
+
} catch (e) {
|
|
34
|
+
// Fall back to python
|
|
35
|
+
try {
|
|
36
|
+
execSync('python --version', { stdio: 'ignore' });
|
|
37
|
+
return 'python';
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error('Error: Python is not installed or not in PATH');
|
|
40
|
+
console.error('Please install Python 3.8 or higher');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const pythonCmd = findPython();
|
|
47
|
+
|
|
48
|
+
// Spawn the Python GUI
|
|
49
|
+
const app = spawn(pythonCmd, [pomeraPath], {
|
|
50
|
+
stdio: 'inherit',
|
|
51
|
+
cwd: path.join(__dirname, '..'),
|
|
52
|
+
detached: process.platform !== 'win32' // Detach on non-Windows
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Handle process exit
|
|
56
|
+
app.on('close', (code) => {
|
|
57
|
+
process.exit(code || 0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Handle errors
|
|
61
|
+
app.on('error', (err) => {
|
|
62
|
+
console.error('Failed to start Pomera:', err.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Forward signals
|
|
67
|
+
process.on('SIGINT', () => app.kill('SIGINT'));
|
|
68
|
+
process.on('SIGTERM', () => app.kill('SIGTERM'));
|
package/core/database_schema.py
CHANGED
|
@@ -398,6 +398,21 @@ class DataTypeConverter:
|
|
|
398
398
|
"""
|
|
399
399
|
import json
|
|
400
400
|
|
|
401
|
+
# Handle None or empty string for all types
|
|
402
|
+
if value_str is None or value_str == '':
|
|
403
|
+
if data_type in ('json', 'array'):
|
|
404
|
+
return [] if data_type == 'array' else {}
|
|
405
|
+
elif data_type == 'str':
|
|
406
|
+
return ''
|
|
407
|
+
elif data_type == 'int':
|
|
408
|
+
return 0
|
|
409
|
+
elif data_type == 'float':
|
|
410
|
+
return 0.0
|
|
411
|
+
elif data_type == 'bool':
|
|
412
|
+
return False
|
|
413
|
+
else:
|
|
414
|
+
return ''
|
|
415
|
+
|
|
401
416
|
if data_type == 'str':
|
|
402
417
|
return value_str
|
|
403
418
|
elif data_type == 'int':
|
|
@@ -407,6 +422,14 @@ class DataTypeConverter:
|
|
|
407
422
|
elif data_type == 'bool':
|
|
408
423
|
return value_str == '1'
|
|
409
424
|
elif data_type in ('json', 'array'):
|
|
410
|
-
|
|
425
|
+
# Handle whitespace-only strings as empty
|
|
426
|
+
if not value_str.strip():
|
|
427
|
+
return [] if data_type == 'array' else {}
|
|
428
|
+
try:
|
|
429
|
+
return json.loads(value_str)
|
|
430
|
+
except json.JSONDecodeError as e:
|
|
431
|
+
# Debug: print what value caused the error
|
|
432
|
+
print(f"DEBUG: JSON parse failed for data_type='{data_type}': value_str='{value_str[:100]}...' error={e}")
|
|
433
|
+
return [] if data_type == 'array' else {}
|
|
411
434
|
else:
|
|
412
435
|
return value_str # Fallback to string
|
|
@@ -308,8 +308,10 @@ class DatabaseSchemaManager:
|
|
|
308
308
|
for index_sql in table_indexes:
|
|
309
309
|
# Extract index name from CREATE INDEX statement
|
|
310
310
|
parts = index_sql.split()
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
# "CREATE INDEX IF NOT EXISTS idx_name ON table(...)"
|
|
312
|
+
# parts: [0]=CREATE [1]=INDEX [2]=IF [3]=NOT [4]=EXISTS [5]=idx_name
|
|
313
|
+
if len(parts) >= 6 and parts[0].upper() == "CREATE" and parts[1].upper() == "INDEX":
|
|
314
|
+
index_name = parts[5] # After "CREATE INDEX IF NOT EXISTS"
|
|
313
315
|
expected_indexes.add(index_name)
|
|
314
316
|
|
|
315
317
|
# Get existing indexes
|
|
@@ -143,6 +143,27 @@ class NestedSettingsProxy:
|
|
|
143
143
|
"""Return a copy of the underlying data as a regular dictionary."""
|
|
144
144
|
return self._data.copy()
|
|
145
145
|
|
|
146
|
+
def pop(self, key: str, *args):
|
|
147
|
+
"""Remove and return a value from the nested settings.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
key: Key to remove
|
|
151
|
+
*args: Optional default value if key not found
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
The removed value, or default if provided and key not found
|
|
155
|
+
"""
|
|
156
|
+
if args:
|
|
157
|
+
result = self._data.pop(key, args[0])
|
|
158
|
+
else:
|
|
159
|
+
result = self._data.pop(key)
|
|
160
|
+
|
|
161
|
+
# Save the change to database
|
|
162
|
+
full_path = f"{self._parent_key}"
|
|
163
|
+
self._settings_manager.set_setting(full_path, self._data)
|
|
164
|
+
|
|
165
|
+
return result
|
|
166
|
+
|
|
146
167
|
def _update_nested_value(self, data: Dict[str, Any], path: str, value: Any) -> None:
|
|
147
168
|
"""Update value in nested dictionary using dot notation."""
|
|
148
169
|
keys = path.split('.')
|
|
@@ -1112,8 +1133,10 @@ class DatabaseSettingsManager:
|
|
|
1112
1133
|
|
|
1113
1134
|
critical_issues = [i for i in issues if i.severity == 'critical']
|
|
1114
1135
|
if critical_issues:
|
|
1115
|
-
|
|
1116
|
-
|
|
1136
|
+
# Log specific issues for debugging but allow import to proceed
|
|
1137
|
+
for issue in critical_issues:
|
|
1138
|
+
self.logger.warning(f"Import validation issue: {issue.location} - {issue.message}")
|
|
1139
|
+
self.logger.warning(f"Imported settings have {len(critical_issues)} validation issues - proceeding anyway")
|
|
1117
1140
|
|
|
1118
1141
|
# Save imported settings
|
|
1119
1142
|
success = self.save_settings(settings_data)
|
package/core/dialog_manager.py
CHANGED
|
@@ -283,8 +283,8 @@ class DialogManager:
|
|
|
283
283
|
try:
|
|
284
284
|
dialog_settings = self.settings_manager.get_setting("dialog_settings", {})
|
|
285
285
|
|
|
286
|
-
# Validate settings structure
|
|
287
|
-
if not isinstance(dialog_settings, dict):
|
|
286
|
+
# Validate settings structure - accept dict or dict-like objects (NestedSettingsProxy)
|
|
287
|
+
if not isinstance(dialog_settings, dict) and not hasattr(dialog_settings, 'get'):
|
|
288
288
|
self.logger.warning(f"Invalid dialog_settings structure: {type(dialog_settings)}, using defaults")
|
|
289
289
|
return True
|
|
290
290
|
|
|
@@ -663,8 +663,8 @@ class DialogManager:
|
|
|
663
663
|
# Force a fresh read of dialog settings
|
|
664
664
|
dialog_settings = self.settings_manager.get_setting("dialog_settings", {})
|
|
665
665
|
|
|
666
|
-
# Validate settings structure
|
|
667
|
-
if not isinstance(dialog_settings, dict):
|
|
666
|
+
# Validate settings structure - accept dict or dict-like objects (NestedSettingsProxy)
|
|
667
|
+
if not isinstance(dialog_settings, dict) and not hasattr(dialog_settings, 'get'):
|
|
668
668
|
self.logger.error(f"Invalid dialog_settings structure: {type(dialog_settings)}, skipping refresh")
|
|
669
669
|
return
|
|
670
670
|
|
|
@@ -9,6 +9,7 @@ from tkinter import scrolledtext
|
|
|
9
9
|
import platform
|
|
10
10
|
import time
|
|
11
11
|
import threading
|
|
12
|
+
import hashlib
|
|
12
13
|
from typing import Dict, List, Tuple, Optional, Any
|
|
13
14
|
from dataclasses import dataclass
|
|
14
15
|
|
|
@@ -31,7 +32,7 @@ class EfficientLineNumbers(tk.Frame):
|
|
|
31
32
|
|
|
32
33
|
# Configuration
|
|
33
34
|
self.line_number_width = 50 # Adjustable width
|
|
34
|
-
self.debounce_delay =
|
|
35
|
+
self.debounce_delay = 50 # ms - balanced responsiveness vs. efficiency
|
|
35
36
|
self.cache_size_limit = 1000 # Maximum cached line positions
|
|
36
37
|
|
|
37
38
|
# Create widgets
|
|
@@ -280,12 +281,12 @@ class EfficientLineNumbers(tk.Frame):
|
|
|
280
281
|
return None
|
|
281
282
|
|
|
282
283
|
def _get_content_hash(self) -> str:
|
|
283
|
-
"""Get a hash of the current content for change detection."""
|
|
284
|
+
"""Get a hash of the current content for change detection using MD5."""
|
|
284
285
|
try:
|
|
285
286
|
content = self.text.get("1.0", "end-1c")
|
|
286
|
-
# Simple hash based on content length and first/last chars
|
|
287
287
|
if content:
|
|
288
|
-
|
|
288
|
+
# Use MD5 for reliable change detection (truncated for efficiency)
|
|
289
|
+
return hashlib.md5(content.encode('utf-8', errors='replace')).hexdigest()[:16]
|
|
289
290
|
return "empty"
|
|
290
291
|
except Exception:
|
|
291
292
|
return "error"
|